﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using GASS;
using GASS.CUDA;
using GASS.CUDA.Types;

namespace EigenCFACUDANET
{
    class Program
    {
        static void FullDirList(DirectoryInfo dir, List<DirectoryInfo> folders)
        {
            foreach (DirectoryInfo d in dir.GetDirectories())
            {
                folders.Add(d);
            }

        }

        static List<int> readScalesFromFile(string nameFile)
        {
            List<int> listScales = new List<int>();
            StreamReader streamReader = new StreamReader(nameFile);
            string scalesString = "";
            string readingSting;
            while ((readingSting = streamReader.ReadLine()) != null)
            {
                scalesString = scalesString + " " + readingSting;
            }
            streamReader.Close();

            string[] scales = scalesString.Split(' ');
            for (int i = 0; i < scales.Length; i++)
            {
                if (scales[i] != "")
                {
                    listScales.Add(int.Parse(scales[i]));
                }
            }
            return listScales;
        }

        static Int32[] readVecFromFile(string nameFile, uint scale)
        {
            Int32[] vec = new Int32[scale];

            StreamReader streamReader = new StreamReader(nameFile);
            string scalesString = "";
            string readingSting;
            while ((readingSting = streamReader.ReadLine()) != null)
            {
                scalesString = scalesString + " " + readingSting;
            }
            streamReader.Close();

            string[] scales = scalesString.Split(' ');
            int j = 0;
            for (int i = 0; i < scales.Length; i++)
            {
                if (scales[i] != "")
                {
                    vec[j] = Int32.Parse(scales[i]);
                    j++;
                }
            }

            return vec;
        }

        static void Main(string[] args)
        {//"D:\gsv\EigenCFA\SmartGenerator\SmartGenerator\bin\Debug\Tests\SimpleTests" "D:\gsv\EigenCFA\ResTests"
            string resPath = @"D:\gsv\EigenCFA\ResTests";//args[1];//@"F:\learn\learn\6_sem\Kursach\MyDevCode\ResTests";
            DirectoryInfo di = new DirectoryInfo(@"D:\gsv\EigenCFA\SmartGenerator\SmartGenerator\bin\Debug\Tests\SimpleTests");//)args[0]);//@"F:\learn\learn\6_sem\Kursach\MyDevCode\gpu-sa\SmartGenerator\SmartGenerator\bin\Debug\Tests\SimpleTests");
            List<DirectoryInfo> folders = new List<DirectoryInfo>();
            FullDirList(di, folders);

            CUDA cuda = new CUDA(0, true);
            foreach (DirectoryInfo folder in folders)
            {
       //         try
   //             {
                    if (folder.GetFiles("Fun.txt").Length == 0)
                        break;
                    if (folder.GetFiles("Arg1.txt").Length == 0)
                        break;
                    if (folder.GetFiles("Arg2.txt").Length == 0)
                        break;
                    if (folder.GetFiles("scales.txt").Length == 0)
                        break;
                    startEigenCFA(cuda, 2, Path.Combine(folder.FullName, "scales.txt"), Path.Combine(folder.FullName, "Fun.txt"),
                        Path.Combine(folder.FullName, "Arg1.txt"), Path.Combine(folder.FullName, "Arg2.txt"),
                        resPath);
  //              }
  //              catch (Exception e)
    //            {
   //             }
            }


            //  startEigenCFA(2, "scales.txt", "Fun.txt", "Arg1.txt", "Arg2.txt");
        }

        static void startEigenCFA(CUDA cuda, int numE, string pathToScales, string pathToFun, string pathToArg1, string pathToArg2, string resPath)
        {

            List<int> scales = readScalesFromFile(pathToScales);

            uint scaleCall = (uint)scales[0];
            uint scaleLam = (uint)scales[1];
            uint scaleVar = (uint)scales[2];
            uint scaleExp = scaleLam + scaleVar;
            uint scaleM = scaleLam + 2;

            Int32[] Fun = readVecFromFile(pathToFun, scaleCall);
            Int32[] Arg1 = readVecFromFile(pathToArg1, scaleCall);
            Int32[] Arg2 = readVecFromFile(pathToArg2, scaleCall);

            System.Console.WriteLine("c " + scaleCall + " l " + scaleLam + " e " + numE);

  //          System.Console.Read();

            //Console.WriteLine();
            //Console.WriteLine("Список CUDA-оборудования");
            //Console.WriteLine();

            var devices = cuda.Devices;

            //Console.WriteLine("Количество устройств: " + devices.Count());
            //Console.WriteLine();

            int numTimeIter = 5;
            double fullTime = 0;

            Stopwatch stopWatch = new Stopwatch();

            for (int timeIter = 0; timeIter < numTimeIter; timeIter++)
            {
                stopWatch.Start();
                cuda.LoadModule("createStore.cubin");

                //ident. start Store
                CUfunction func = cuda.GetModuleFunction("createStartStoreKernel");

                int numberThreadX = 32;
                int numberThreadY = 32;

                cuda.SetFunctionBlockShape(func, numberThreadX, numberThreadY, 1);


                CUdeviceptr dev_Store = cuda.Allocate(scaleExp * scaleM * sizeof(int));
                //1 ptr devStore
                cuda.SetParameter(func, 0, (uint)dev_Store.Pointer);
                //2 scaleExp 
                cuda.SetParameter(func, IntPtr.Size, (uint)scaleExp);
                //3 scalem
                cuda.SetParameter(func, IntPtr.Size + sizeof(uint), (uint)scaleM);
                //4 scaleVar
                cuda.SetParameter(func, IntPtr.Size + 2 * sizeof(uint), (uint)scaleVar);

                //общий размер параметров
                cuda.SetParameterSize(func, (uint)(IntPtr.Size + 3 * sizeof(uint)));

                //Запускаем заполнение Store
                cuda.Launch(func, ((int)(scaleM + numberThreadX - 1) / numberThreadX), (int)(scaleExp + numberThreadY - 1) / numberThreadY);

                cuda.SynchronizeContext();

                int[] Store = new int[scaleExp * scaleM];
                cuda.CopyDeviceToHost<int>(dev_Store, Store);

  //              printStore(Store, scaleExp, scaleM, "Store:");


                //EigenCFA

                CUdeviceptr dev_semaphore = cuda.Allocate(scaleExp * sizeof(int));
                cuda.Memset(dev_semaphore, 0, scaleExp * sizeof(int));

                int[] rep = new int[1];
                rep[0] = 0;
                cuda.LoadModule("eigenCFA.cubin");

                CUfunction funcEigenCFA = cuda.GetModuleFunction("eigenCFA");

                int numThreadX = 1024;
                int blockX = (int)((scaleCall + numThreadX - 1) / numThreadX);
                int blockY = 2;

                cuda.SetFunctionBlockShape(funcEigenCFA, numThreadX, 1, 1);

                //копируем всё на GPGPU
                CUdeviceptr dev_Fun = cuda.CopyHostToDevice<Int32>(Fun);
                CUdeviceptr dev_Arg1 = cuda.CopyHostToDevice<Int32>(Arg1);
                CUdeviceptr dev_Arg2 = cuda.CopyHostToDevice<Int32>(Arg2);
                CUdeviceptr dev_rep = cuda.CopyHostToDevice<Int32>(rep);

                //1 ptr devFun
                cuda.SetParameter(funcEigenCFA, 0, (uint)dev_Fun.Pointer);
                //2 ptr devArg1
                cuda.SetParameter(funcEigenCFA, IntPtr.Size, (uint)dev_Arg1.Pointer);
                //3 ptr devArg2
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 2, (uint)dev_Arg2.Pointer);
                //4 ptr devStore
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 3, (uint)dev_Store.Pointer);
                //5 ptr devSemaphore
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 4, (uint)dev_semaphore.Pointer);
                //6 ptr devRep
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 5, (uint)dev_rep.Pointer);
                //7 scaleM
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 6, (uint)scaleM);
                //8 scaleCall
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 6 + sizeof(uint), scaleCall);
                //9 scaleLam
                cuda.SetParameter(funcEigenCFA, IntPtr.Size * 6 + 2 * sizeof(uint), scaleLam);

                cuda.SetParameterSize(funcEigenCFA, (uint)(IntPtr.Size * 6 + 3 * sizeof(uint)));

                int iter = 1;
                do
                {
//                    System.Console.WriteLine("Iter = " + iter);
                    iter++;
                    rep[0] = 0;
                    cuda.CopyHostToDevice(dev_rep, rep);
                    cuda.Launch(funcEigenCFA, blockX, 2);

                    cuda.SynchronizeContext();

                    cuda.CopyDeviceToHost<int>(dev_rep, rep);
                } while (rep[0] != 0);

                // int[] Store = new int[scaleExp* scaleM];
                cuda.CopyDeviceToHost<int>(dev_Store, Store);

     //           printStore(Store, scaleExp, scaleM, "Store:");


                cuda.Free(dev_Store);
                cuda.Free(dev_Arg1);
                cuda.Free(dev_Fun);
                cuda.Free(dev_Arg2);
                cuda.Free(dev_rep);
                cuda.Free(dev_semaphore);
                stopWatch.Stop();
                fullTime = fullTime + stopWatch.ElapsedMilliseconds;
            }

            System.Console.WriteLine(fullTime/ numTimeIter);

            writeTimeToFile(resPath, "CUDANET.txt", fullTime / numTimeIter, (int)(scaleLam * 3 + scaleCall), numE);

        }

        static void printStore(int[] store, uint row, uint column, string s)
        {
            System.Console.WriteLine(s);
            for (int i = 0; i < row; i++)
            {
                System.Console.Write(i + " ");
                for (int j = 0; j < column; j++)
                {
                    System.Console.Write(store[j + column * i] + " ");
                }
                System.Console.WriteLine();
            }
        }

        static void writeTimeToFile(string pathFolder, string nameFile, double time, int numTerm, int numE)
        {
            string pathFile = Path.Combine(pathFolder, nameFile);
            FileStream fileStream = new FileStream(pathFile, FileMode.Append);
            using (StreamWriter sw = new StreamWriter(fileStream))
            {
                fileStream.Seek(0, SeekOrigin.End);

                sw.WriteLine(numTerm + " " + (time/1000).ToString().Replace(",","."));// + " " + numE);

                sw.Close();
            }
        }

    }
}
